package controller

import (
	"net/http"

	. "gddgame.cc/galaxy/common/dsl/web"
	"gddgame.cc/galaxy/common/thirdparty"
	"gddgame.cc/galaxy/core/models"
	"gddgame.cc/galaxy/server/web"
	"gddgame.cc/galaxy/utils"
	"gddgame.cc/galaxy/utils/def"
	"gddgame.cc/galaxy/utils/uuid"
)

const (
	WechatApp       = "wechat_app"
	WechatPc        = "wechat_pc"
	QQApp           = "qq_app"
	QQPc            = "qq_pc"
	FacebookApp     = "facebook_app"
	AppleGameCenter = "apple_game_center"
	GoogleGamePlay  = "google_game_play"
	GooglePc        = "google_pc"
	MiGame          = "mi_game"
	HuaweiApp       = "huawei_app"
	UcGame          = "uc_game"
)

var (
	oauthMap = map[string]string{
		WechatApp:       "wechat",
		WechatPc:        "wechat",
		QQApp:           "qq",
		QQPc:            "qq",
		FacebookApp:     "facebook",
		AppleGameCenter: "apple_game_center",
		GoogleGamePlay:  "google_game_play",
		GooglePc:        "google",
		MiGame:          "mi_game",
		HuaweiApp:       "huawei",
		UcGame:          "uc_game",
	}
)

type Client interface {
	GetOAuthUrl(redirect string) string
}

func Redirect(context def.Context, app *models.App, channel string, callback string) error {
	var url string
	switch channel {
	case WechatPc:
		url = app.Wechat.GetOAuthUrl(callback, "")
	case QQPc:
		url = app.QQ.GetOAuthUrl(callback, "")
	case GooglePc:
		url = app.Google.GetOAuthUrl(callback, "")
	}
	web.Redirect(context, url, http.StatusFound)
	return nil
}

func Callback(context def.Context, app *models.App, channel string, code string, state string, userOpenId string, originId string, callback string) (*models.UserOpen, error) {
	var data *thirdparty.OauthData
	var err error
	var openIdField string
	var unionIdField string
	openIdField = oauthMap[channel] + "_open_id"
	unionIdField = oauthMap[channel] + "_union_id"
	switch channel {
	case WechatPc:
		data, err = app.Wechat.WebAuthorize(code)
	case QQPc:
		data, err = app.QQ.WebAuthorize(code, callback)
	case GooglePc:
		data, err = app.Google.WebAuthorize(code, callback)
	}
	if err != nil {
		// 重新授权
		return nil, Redirect(context, app, channel, callback)
	}
	userOpen, err := getOauthOpen(context, app, userOpenId, openIdField, unionIdField, data, nil)
	if err != nil {
		return nil, err
	}
	if originId != "" && userOpen.LastTime == nil {
		// 第一次访问，绑定绑定originId
		if err := UpdateOpen(context, app, userOpen.Id, map[string]interface{}{
			"origin_open_id": originId,
		}); err != nil {
			context.Errorf("Bind Origin Fail:%s", err)
		}
		userOpen.OriginOpenId = originId
	}
	return userOpen, nil
}

func OauthList(context def.Context, app *models.App, userOpenId string) ([]string, error) {
	list := []string{}
	m := map[string]struct{}{}
	// 更新openId
	userOpen, err := models.UserOpenModel.PK(userOpenId).OneInterface()
	if err != nil {
		return nil, SystemExceptionDetail("BindOpenFail", err)
	}
	for _, value := range oauthMap {
		openIdField := value + "_open_id"
		if v, ok := userOpen[openIdField]; ok && v != "" {
			if _, ok := m[value]; !ok {
				m[value] = struct{}{}
				list = append(list, value)
			}
		}
	}
	return list, nil
}

// 移动app授权
func Oauth(context def.Context, app *models.App, channel string, code string, state string, userOpenId string) (*models.UserOpen, error) {
	var data *thirdparty.OauthData
	var realData *thirdparty.RealData
	var err error
	var openIdField string
	var unionIdField string
	openIdField = oauthMap[channel] + "_open_id"
	unionIdField = oauthMap[channel] + "_union_id"
	switch channel {
	case WechatApp:
		data, err = app.Wechat.WebAuthorize(code)
	case MiGame:
		data, realData, err = app.Mi.LoginValidate(code, state)
	}
	if err != nil {
		return nil, SystemExceptionDetail(channel+"授权失败", err)
	}
	return getOauthOpen(context, app, userOpenId, openIdField, unionIdField, data, realData)
}

func AppOauth(context def.Context, app *models.App, channel string, openId string, accessToken string, userOpenId string) (*models.UserOpen, error) {
	var data *thirdparty.OauthData
	var openIdField string
	var unionIdField string
	openIdField = oauthMap[channel] + "_open_id"
	unionIdField = oauthMap[channel] + "_union_id"
	switch channel {
	case WechatApp:
		data = &thirdparty.OauthData{
			OpenId:  openId,
			UnionId: openId,
		}
	case QQApp:
		// todo 进行用户映射：http://wiki.connect.qq.com/?s=union
		user, err := app.QQ.GetUserInfo(openId, accessToken)
		if err != nil {
			return nil, ValidExceptionDetail("OAUTH_FAIL", err)
		}
		open, err := app.QQ.GetOpenId(accessToken)
		if err != nil {
			return nil, ValidExceptionDetail("OAUTH_FAIL", err)
		}
		data = app.QQ.ParseUserInfo(user, open)
	case FacebookApp:
		// todo 进行用户映射：https://developers.facebook.com/docs/apps/for-business/
		user, err := app.Facebook.GetUserInfo(openId, accessToken)
		if err != nil {
			return nil, ValidExceptionDetail("OAUTH_FAIL", err)
		}
		data = app.Facebook.ParseUserInfo(user)
	case AppleGameCenter:
		// todo https://developer.apple.com/documentation/gamekit/gklocalplayer/1515407-generateidentityverificationsign?language=objc
		data = &thirdparty.OauthData{
			OpenId:  openId,
			UnionId: openId,
		}
	case GoogleGamePlay:
		// todo https://developers.google.com/identity/sign-in/android/backend-auth
		user, err := app.Google.GetUserInfo(accessToken)
		if err != nil {
			return nil, ValidExceptionDetail("OAUTH_FAIL", err)
		}
		data = app.Google.ParseUserInfo(user)
	}
	return getOauthOpen(context, app, userOpenId, openIdField, unionIdField, data, nil)
}
func AppOfflineOauth(context def.Context, app *models.App, channel string, openId string, nickname string, userOpenId string) (*models.UserOpen, error) {
	var data *thirdparty.OauthData
	var openIdField string
	var unionIdField string
	openIdField = oauthMap[channel] + "_open_id"
	unionIdField = oauthMap[channel] + "_union_id"
	data = &thirdparty.OauthData{
		OpenId:   openId,
		UnionId:  openId,
		NickName: nickname,
		Avatar:   "",
	}
	switch channel {
	case FacebookApp:
		// todo 进行用户映射：https://developers.facebook.com/docs/apps/for-business/
	case AppleGameCenter:
		// todo https://developer.apple.com/documentation/gamekit/gklocalplayer/1515407-generateidentityverificationsign?language=objc
	case GoogleGamePlay:
		// todo https://developers.google.com/identity/sign-in/android/backend-auth
	}
	return getOauthOpen(context, app, userOpenId, openIdField, unionIdField, data, nil)
}
func getOauthOpen(context def.Context, app *models.App, userOpenId string, openIdField string, unionIdField string, data *thirdparty.OauthData, realData *thirdparty.RealData) (*models.UserOpen, error) {
	var userOpen models.UserOpen
	var user map[string]interface{}
	var err error
	var ok bool
	if userOpenId != "" {
		// 更新openId
		if _, err = models.UserOpenModel.PK(userOpenId).Find(&userOpen); err != nil {
			return nil, SystemExceptionDetail("BindOpenFail", err)
		}
		user, err = models.UserOpenModel.PK(userOpen.Id).OneInterface()
		if err != nil {
			return nil, SystemExceptionDetail("BindOpenFail", err)
		}
		if err = bindOpenId(context, app, user, data, openIdField); err != nil {
			return nil, SystemExceptionDetail("BindOpenFail", err)
		}
		user, err = models.UserModel.PK(userOpen.UserId).OneInterface()
		if err != nil {
			return nil, SystemExceptionDetail("BindOpenFail", err)
		}
		if err = bindUnionId(context, user, data, realData, unionIdField); err != nil {
			return nil, SystemExceptionDetail("BindOpenFail", err)
		}
	} else {
		if ok, _ = models.UserOpenModel.Query().Where("=", openIdField, data.OpenId).Where("=", "app_id", app.Id).One(&userOpen); ok {
			user, err = models.UserModel.PK(userOpen.UserId).OneInterface()
			if err != nil {
				return nil, SystemExceptionDetail("BindUnionFail", err)
			}
			if err = bindUnionId(context, user, data, realData, unionIdField); err != nil {
				return nil, SystemExceptionDetail("BindUnionFail", err)
			}
		} else {
			return generateOauthUser(context, app, openIdField, unionIdField, data, realData)
		}
	}
	return &userOpen, nil
}
func generateOauthUser(context def.Context, app *models.App, openIdField string, unionIdField string, data *thirdparty.OauthData, realData *thirdparty.RealData) (*models.UserOpen, error) {
	var user models.User
	var id int
	var ok bool
	if ok, _ = models.UserModel.Query().Where("=", unionIdField, data.UnionId).One(&user); !ok {
		info := map[string]interface{}{
			"guest_id":   "",
			"name":       "",
			"password":   "",
			"email":      "",
			"mobile":     "",
			unionIdField: data.UnionId,
		}
		if realData != nil {
			info["third_real_status"] = realData.RealStatus
			info["third_real_age"] = realData.RealAge
			info["third_is_under_age"] = realData.IsUnderAge
		}
		id6, err := models.UserModel.Query().Insert(info)
		if err != nil {
			return nil, SystemExceptionDetail("用户添加失败", err)
		}
		id = utils.ToInt(id6)
	} else {
		id = user.Id
	}
	if _, err := models.UserOpenModel.Query().Insert(map[string]interface{}{
		"id":        app.EncodeUser(id),
		"user_id":   id,
		"app_id":    app.Id,
		"nick_name": data.NickName,
		"avatar":    data.Avatar,
		"token":     uuid.RandomString(),
		openIdField: data.OpenId,
	}); err != nil {
		return nil, SystemExceptionDetail("用户添加失败", err)
	}
	return GetOpen(context, app, app.EncodeUser(id))
}

func bindOpenId(context def.Context, app *models.App, user map[string]interface{}, data *thirdparty.OauthData, openIdField string) error {
	if data.OpenId == "" && data.UnionId == "" {
		return nil
	}
	flag := false
	info := make(map[string]interface{})
	if v, ok := user[openIdField]; ok {
		if v != data.OpenId {
			flag = true
			info[openIdField] = data.OpenId
		}
	}
	if flag {
		return UpdateOpen(context, app, user["id"].(string), info)
	}
	return nil
}

func bindUnionId(context def.Context, user map[string]interface{}, data *thirdparty.OauthData, realData *thirdparty.RealData, unionIdField string) error {
	if data.OpenId == "" && data.UnionId == "" {
		return nil
	}
	flag := false
	info := make(map[string]interface{})
	if v, ok := user[unionIdField]; ok {
		if v != data.UnionId {
			flag = true
			info[unionIdField] = data.UnionId
		}
	}

	if realData != nil {
		info["third_real_status"] = realData.RealStatus
		info["third_real_age"] = realData.RealAge
		info["third_is_under_age"] = realData.IsUnderAge
	}
	if flag {
		if err := UpdateUser(context, utils.ToInt(user["id"]), info); err != nil {
			return err
		}
	}
	return nil
}
